创建一个完善的vue3响应式系统

您所在的位置:网站首页 vue3 proxy优缺点 创建一个完善的vue3响应式系统

创建一个完善的vue3响应式系统

2023-06-04 16:34| 来源: 网络整理| 查看: 265

vue重要的特性就是响应式,这次使用ES6的proxy特性,手动实现一个响应式,深入理解vue3响应式原理

副作用函数 副作用函数: 是指会产生副作用的函数,直接或间接的影响外部参数产生变化 var num = 1 function fns() { num = 2 // 修改全局变量,产生副作用 } fns() console.log(num) // 2 实现一个简单的响应式功能 const obj = { text: "hello world" }; function effect() { document.body.innerHTML = obj.text } effect() obj.text = 'hello vue3' 如上述代码,当副作用函数执行后,页面显示hello world,如果再次给obj.data重新赋值,不能触发副作用函数直接修改,没有响应式的执行。 如果想要设置成响应式,就要监听它的读取和设置操作

1.当读取时,会把当前的副作用函数存储在一个'桶'中 Snipaste_2023-05-27_10-42-29.png 2.在重新赋值操作中,会将存储的副作用函数取出再重新执行 Snipaste_2023-05-27_14-42-10.png 在vue2中,实现响应式的是ES5的Object.defineProperty而在vue3中,实现响应式的是ES6的代理对象proxy来实现

基本思路:

// 存储副作用函数的桶 const bucket = new Set(); // 原始数据 const data = { text: "hello world" }; // 对原始数据的代理 const obj = new Proxy(data, { // 拦截读取操作 get(target, key) { // 将副作用函数 effect 添加到存储副作用函数的桶中 bucket.add(effect); // 返回属性值 return target[key]; }, // 拦截设置操作 set(target, key, newVal) { // 设置属性值 target[key] = newVal; // 把副作用函数从桶里取出并执行 bucket.forEach((fn) => fn()); // 返回 true 代表设置操作成功 return true; }, }); 使用一个定时器可以验证当重新改变参数时,会再次触发副作用函数执行 function effect() { document.body.innerHTML = obj.text } effect() setTimeout(() => { obj.text = 'hello vue3' },1000) 一个完善的响应式系统

现在一个基本响应式系统创建好了,但是还有一些问题:

当前的副作用函数写死了,如果副作用函数不是'effect'时,就不能正确实现响应式 解决方法:提供一个用来注册副作用函数的机制 // 用一个全局变量存储被注册的副作用函数 let activeEffect; // effect 函数用于注册副作用函数 function effect(fn) { activeEffect = fn; // 执行副作用函数 fn(); } // 参数是一个匿名的副作用函数 effect(() => { document.body.innerHTML = obj.text; });

修改代理对象中的get的存储方式

get(target, key) { // bucket.add(effect); 之前的 // 将 activeEffect 中存储的副作用函数收集到“桶”中 if (activeEffect) { // 新增 bucket.add(activeEffect) // 新增 } // 新增 return target[key] } 如果定时器中添加一个不存在的参数,并且没有与副作用函数建立联系,但还是会触发了响应式监听 根本原因:没有在副作用函数与被操作的字段之间建立明确的关系,无论是读取的哪一个属性,都会当前桶中存储的副作用函数取出并执行 effect(() => { console.log('执行副作用函数'); // 测试可以会执行两次 document.body.innerHTML = obj.text; }); setTimeout(() => { // obj.text = "heeeeeeeeee"; obj.noText = '不存在' }, 1000); 解决方法:副作用函数与被被操作的字段之间建立联系,将桶改成Map,将参数和副作用函数挂钩起来

基本思路:

// 创建一个新桶来存储副作用函数,包含key和value const bucket = new WeakMap(); const obj = new Proxy(data, { get(target, key) { // target:当前对象,key:触发监听的key // 没有正在执行的副作用函数 直接返回 if (!activeEffect) return target[key]; // 从这个桶中取出一个Map类型(key -> value) let depsMap = bucket.get(target); // 不存在,则创建一个Map与target关联 if (!depsMap) { bucket.set(target, (depsMap = new Map())); } // 根据key判断每个key上是否存在对应的副作用函数 let deps = depsMap.get(key); // 不存在,则新建一个new Set,并与key关联 if (!deps) { depsMap.set(key, (deps = new Set())); } // 最后将当前激活的副作用函数添加到桶中 deps.add(activeEffect); return target[key]; // 将 activeEffect 中存储的副作用函数收集到“桶”中 // if(activeEffect) { // bucket.add(activeEffect) // } }, set(target, key, newVal) { target[key] = newVal; // 根据target从桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target) if(!depsMap) return // 根据key取得当前对应的副作用函数 const effects = depsMap.get(key) // 执行副作用函数 effects && effects.forEach(fn => fn()) // // 把副作用函数从桶里取出并执行 // bucket.forEach((fn) => fn()); // // 返回 true 代表设置操作成功 // return true; }, });

weakMap、Map和Set的关系图

Snipaste_2023-05-28_14-44-34.png 完整代码:可以将get和set中的逻辑封装在track和trigger函数中,极大的提高灵活性

const data = { text: "hello world" }; // 用一个全局变量存储被注册的副作用函数 let activeEffect; // 创建一个新桶来存储副作用函数,包含key和value const bucket = new WeakMap(); const obj = new Proxy(data, { get(target, key) { // target:当前对象,key:触发监听的key track(target, key) return target[key]; }, set(target, key, newVal) { target[key] = newVal; trigger(target, key) }, }); // effect 函数用于注册副作用函数 function effect(fn) { activeEffect = fn; // 执行副作用函数 fn(); } // 参数是一个匿名的副作用函数 effect(() => { console.log("执行副作用函数"); document.body.innerHTML = obj.text; }); setTimeout(() => { obj.text = 'hello vue3' // obj.noText = "不存在"; }, 1000); // track函数 function track(target, key) { // 没有正在执行的副作用函数 直接返回 if (!activeEffect) return target[key]; // 从这个桶中取出一个Map类型(key -> value) let depsMap = bucket.get(target); // 不存在,则创建一个Map与target关联 if (!depsMap) { bucket.set(target, (depsMap = new Map())); } // 根据key判断每个key上是否存在对应的副作用函数 let deps = depsMap.get(key); // 不存在,则新建一个new Set,并与key关联 if (!deps) { depsMap.set(key, (deps = new Set())); } // 最后将当前激活的副作用函数添加到桶中 deps.add(activeEffect); } // trigger函数 function trigger(target, key) { // 根据target从桶中取得depsMap,它是key --> effects const depsMap = bucket.get(target) if(!depsMap) return // 根据key取得当前对应的副作用函数 const effects = depsMap.get(key) // 执行副作用函数 effects && effects.forEach(fn => fn()) }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3